/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.protocol;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import cgl.narada.matching.rtp.RtpEvent;
import cgl.narada.util.ByteUtilities;


/**
 * The EventRoutingProtocol is responsible for updating routing information
 * associated with events. The routing information would vary depending on the
 * link over which the event was received. Any given node could be connected
 * to multiple links which have different gateway behavior. The event routing
 * protocol needs to handle every such case.
 *
 * @author Shrideep Pallickara
 * $Revision$
 * $Date$
 */
public class RtpEventRoutingProtocol implements ProtocolDebugFlags {
   /** The node address of this node */
   private byte[] nodeAddress;

   /** The system level of the system to which this node is attached to */
   private int systemLevel;

   /** Maintains information about revelant gatekeepers within the system */
   private GatewayInfo gatewayInfo;
  
  /* Reference to the profile propagation protocol */
  RtpProfilePropagation rtpPropagationProtocol;
  
  /** A handle to the protocol Handler */
  private ProtocolHandler protocolHandler;
  
  /** Used for routing events to clients after the destinations are computed
      by a matchingtree at level-0 */
  private ClientConnectionHandler clientConnectionHandler;
  
  /** The destination ID of this node */
  private Destinations thisDestination;
  
  /** Temporary variable which would enable us to duplicate detect messages
      issued in a 1 publisher multi-subscriber setting. To be replaced
      with {clientID, timestamp, sequenceNumber, incarnation} based DD algo.
  */
  private long Test_DuplicateDetect_Stamp = 0;
  private String moduleName = "RtpEventRoutingProtocol: ";
  
  /** The constructor requires both the nodeAdress which it would use to
      update routing information associated with a certain received event,
      and also the gatewayInfo which would be used to compute the next most
      efficient hop
      @param _nodeAddress The node address of the node in question.
      @param gatewayInfo Reference to the connectivity graph hosted at the node
      @param rtpPropagationProtocol Reference to the RTP profile propagation
      protocol which would provide uswith destinations at different levels.
      @param clientConnectionHandler This is useful for routing events  to
      clients within the destination lists that are generated.
      @param protocolHandler A handle to the protocol Handler
  */
  public 
  RtpEventRoutingProtocol(NodeAddress _nodeAddress, 
			  GatewayInfo gatewayInfo, 
			  RtpProfilePropagation rtpPropagationProtocol,
			  ClientConnectionHandler clientConnectionHandler, 
			  ProtocolHandler protocolHandler) {
    nodeAddress = _nodeAddress.getAddressInBytes();
    thisDestination = new Destinations(nodeAddress);
    this.gatewayInfo = gatewayInfo;
    this.clientConnectionHandler = clientConnectionHandler;
    this.rtpPropagationProtocol = rtpPropagationProtocol;
    this.protocolHandler = protocolHandler;
    systemLevel = gatewayInfo.getSystemLevel();
  }
  
  /** This constructor is in place for stand almone testing of the class
      @param _nodeAddress The node address of the node in question.
  */
  public RtpEventRoutingProtocol(NodeAddress _nodeAddress) {
    nodeAddress = _nodeAddress.getAddressInBytes();
  }
  
  /** This method is responsible for the calculation of destination lists
      associated with the event. If this node can compute destinations at
      a certain level it should do so and if it cannot calculate destinations
      at a certain level, it should defer the calculation of destination lists
      to the appropriate gateways.
      The method should be able to deal with both, upward and downward
      dissemination of events within the system. By <i>upward</i> we mean that
      the event flows from the client to the server to the cluster gateway and
      so on.
      
      @param receivedEventBytes The event and the event routing information
      encapsulated within a stream of bytes
  */
  public void processEventReceived(byte[] receivedEventBytes) {
    ByteArrayInputStream baInputStream = 
      new ByteArrayInputStream(receivedEventBytes);
    DataInputStream din = new DataInputStream(baInputStream);
    RtpEvent rtpEvent;
    Destinations toReach;
    Destinations traversedSoFar;
    
    try {
      /** By passing the protocol Header */
      din.readByte();
      
      /** Unmarshalling the RtpEvent */
      int rtpEventLength = din.readInt();
      byte[] rtpEventBytes = new byte[rtpEventLength];
      din.readFully(rtpEventBytes);
      
      rtpEvent = new RtpEvent(rtpEventBytes);
      
      /** Unmarshall the traversedSoFar and toReach destinations contained in
	  the protocol packet*/
      byte[] toReachBytes = new byte[4 * (systemLevel + 1)];
      din.readFully(toReachBytes);
      
      byte[] traversedSoFarBytes = new byte[4 * (systemLevel + 1)];
      din.readFully(traversedSoFarBytes);
      
      toReach = new Destinations(toReachBytes);
      traversedSoFar = new Destinations(traversedSoFarBytes);
      
      traversedSoFar.markAsTraversedAt(thisDestination);
      
      
      /** Based on toReach we have to decide how exactly we are going to
	  calculate our destinations that need to be reached. */
      
      //System.out.println("ERP:Computing destinations ...");
      calculateDestinations(rtpEvent, toReach, systemLevel - 1);
      
      
      Destinations val = new Destinations(systemLevel);
      val.updateWithDestination(toReach);
      val.markAsTraversedAt(thisDestination);
      val.toString();
      
      if (!traversedSoFar.equals(val)) {
	disseminateEventThroughoutSystem(rtpEvent, toReach, traversedSoFar);
      } else {
	if (RtpEventRoutingProtocol_Debug) {
	  System.out.println(moduleName + 
			     "No more broker routing to be done for event ");
	}
      }
      
      /** There should be a method here which routes the event to clients.
	  Every node is a gateway at level-0 if the node is contained in the
	  routing information */
      routeToClientNodes(rtpEvent, rtpEventBytes);
    } catch (IOException ioe) {
      System.out.println(moduleName + "processEventReceived()::" + 
			 " unmarshalling exception" + ioe);
    }/*end try-catch */
  }
  
  public void processEventReceivedFromClient(byte[] rtpEventBytes) {
    RtpEvent rtpEvent = null;
    
    rtpEventBytes[0] = Protocol.NARADA_RTP_EVENT;
    rtpEvent = new RtpEvent(rtpEventBytes);
    
    /** Now we begin the task of constructing the routing information */
    Destinations toReach = new Destinations(systemLevel);
    toReach.updateWithDestination(gatewayInfo.getListOfDestinationsKnownToNode());
    
    Destinations traversedSoFar = new Destinations(systemLevel);
    traversedSoFar.markAsTraversedAt(thisDestination);
    
    /** If this node is a gateway at different levels we further refine
	the destinations associated with the event */
    for (int i = 0; i < systemLevel; i++) {
      if (rtpPropagationProtocol.isGatewayAtLevel(i + 1)) {
	int dest = 
	  rtpPropagationProtocol.computeDestinationsAtLevel(rtpEvent, i);
	toReach.setDestinationsAtLevel(i, dest);
	
	if (dest == 0) {
	  /** The event shouldn't be disseminated with the level - i */
	  for (int j = i; j >= 0; j--) {
	    toReach.setDestinationsAtLevel(j, dest);
	    
	    //thisDestination.getDestinationsAtLevel(j));
	  }
	  
	  //break;
	}
      }
    }
       
    
    if (RtpEventRoutingProtocol_Debug) {
      System.out.println("ERP:ProcessEventFromClient :: " + 
			 "Server Destinations to be reached ->" + toReach);
    }
    
    disseminateEventThroughoutSystem(rtpEvent, toReach, traversedSoFar);

    /** There should be a method here which routes the event to clients.
	Every node is a gateway at level-0*/
    routeToClientNodes(rtpEvent, rtpEventBytes);
  }
  
  private void routeToClientNodes(RtpEvent rtpEvent, byte[] rtpEventBytes) {
    
    ArrayList destinations = 
      rtpPropagationProtocol.computeClientDestinations(rtpEvent);
    
    if (destinations == null) {
      /** There were no matching destinations */
      if (RtpEventRoutingProtocol_Debug) { 
	System.out.println(moduleName + 
			   "No destinations associated with packets sent from["
			   + rtpEvent.getSource() + "] with RTPMeetingID=" + 
			   rtpEvent.getTopic());
      }
      return;
    }
    
    //destinations.remove(new Integer(rtpEvent.getSource()));
    
    if (destinations.size() == 0) {
      if (RtpEventRoutingProtocol_Debug) {
	System.out.println(moduleName + "routeToClientNodes() ->" + 
			   " No clients can be reached ");
      }
      
      return;
    }
    
    /*clientConnectionHandler.sendToClientNodes(rtpEventBytes, destinations,
      rtpEvent.getSource());
    */
    DispatchUnit dispatchUnit = 
      new DispatchUnit(rtpEventBytes, destinations, rtpEvent.getSource() );
    EntityEventDispatcher.getInstance().addDispatchUnit(dispatchUnit);
  }
  
  /** This method is responsible for routing events throughout the server
      network
      @param rtpEvent The event that needs to be routed.
      @param toReach The destinations that need to be reached.
      @param traversedSoFar The list of destinations that have been traversed
      so far.
  */
  private void disseminateEventThroughoutSystem(RtpEvent rtpEvent, 
						Destinations toReach, 
						Destinations traversedSoFar) {
    String information = toReach.toString() + traversedSoFar.toString();
    Gateway[] hopsToTake = 
      gatewayInfo.hopsToReachDestination(toReach, traversedSoFar);
    
    /** if connection.level < hop.level ... don not send over the hop */
    if (hopsToTake != null) {
      for (int i = 0; i < hopsToTake.length; i++) {
	/** Depending on the kind of hop that you are gonna be sending the
	    connection ... send the relevant connection information over the
	    link */
	byte[] byteStream = 
	  prepareEventBytesToSendOverHop(hopsToTake[i].getLevel(), rtpEvent, 
					 toReach, traversedSoFar);
	protocolHandler.sendToNode(hopsToTake[i], byteStream);
	
	if (RtpEventRoutingProtocol_Debug) {
	  System.out.println(moduleName + "Byte stream sent over hop " + 
			     hopsToTake[i]);
	}
      }
      
      return;
    }
    
    if (RtpEventRoutingProtocol_Debug) {
      System.out.println(" ERP: - No more hops to take");
    }
  }
  
  /** This method is responsible for sending information over a hop. The
      information contained in the Destinations are modified based on the type
      of hop that we are going to be sending this event over */
  private byte[] prepareEventBytesToSendOverHop(int hopLevel, 
						RtpEvent rtpEvent, 
						Destinations toReach, 
						Destinations traversedSoFar) {
    ByteArrayOutputStream baOutputStream = new ByteArrayOutputStream();
    DataOutputStream dout = new DataOutputStream(baOutputStream);
    
    try {
      /** The protocol header */
      dout.writeByte(Protocol.NARADA_RTP_EVENT);


      byte[] rtpEvtBytes = rtpEvent.getBytes();      
      dout.writeInt(rtpEvtBytes.length);


      /** Marshall the rtpEvent */
      dout.write(rtpEvtBytes);
      
      
      /** Marshalling toReach destinations bytes */
      dout.write(toReach.prepareDestinationsToSendOverLevel(hopLevel));
      
      
      /**  Marshalling the Destinations traversedSoFar bytes */
      dout.write(traversedSoFar.prepareDestinationsToSendOverLevel(hopLevel));
      
      dout.flush();
    } catch (IOException ioe) {
      System.out.println("RtpEvtRoutingProto:prepareBytesToSend::" + 
			 " marshalling error " + ioe);
    }
    
    return baOutputStream.toByteArray();
  }
  
  /** This method recursively computes destinations for lower levels.
      The recursion stops if the node in question is not a gateway
      at one of the levels (starting from highest levels to succesively
      lower ones)
      @param rtpEvent The event for which destinations need to be
      computed
      @param toReach The destinations that need to be reached */
  public void calculateDestinations(RtpEvent rtpEvent, Destinations toReach) {
    int _level;
    
    for (_level = systemLevel - 1; _level <= 0; _level--) {
      if (toReach.getDestinationsAtLevel(_level) == 0) {
	/**This implies we need to compute destinations from level i onwards
	   down to level 0*/
	break;
      }
    }
    
    if (_level == -1) {
      /** No server destinations need to be computed, we compute the
	  client destinations and move on*/
      return;
    }
    
    /** Only if its a gateway at (l+1) can it compute destinations at level-l*/
    if (rtpPropagationProtocol.isGatewayAtLevel(_level + 1)) {
      System.out.println("Gateway at level (" + (_level + 1) + 
			 ") computing destinations at " + _level);
      
      int dest = 
	rtpPropagationProtocol.computeDestinationsAtLevel(rtpEvent, _level);
      
      /** This statement prevents a node from being stuck in a infinite
	  recursive process in the event that dest=0 */
      if (dest == 0) {
	return;
      }
      
      toReach.setDestinationsAtLevel(_level, dest);
      calculateDestinations(rtpEvent, toReach);
    }
  }
  
  /** This method recursively computes destinations for lower levels.
      The recursion stops if the node in question is not a gateway
      at one of the levels (starting from highest levels to succesively
      lower ones)
      @param rtpEvent The event for which destinations need to be
      computed
      @param toReach The destinations that need to be reached */
  public void calculateDestinations(RtpEvent rtpEvent, Destinations toReach, 
				    int computeForLevel) {
    //System.out.println("Computing destinations at " + computeForLevel);
    int _level = computeForLevel;
    
    if (_level == -1) {
      /** No server destinations need to be computed, we compute the
          client destinations and move on*/
      return;
    }
    
    for (; _level == 0; _level--) {
      if (toReach.getDestinationsAtLevel(_level) == 0) {
	/**This implies we need to compute destinations from level i onwards
	   down to level 0*/
	break;
      }
    }
    
    /** Only if its a gateway at (l+1) can it compute destinations at level-l*/
    if (rtpPropagationProtocol.isGatewayAtLevel(_level + 1)) {
      int dest = 
	rtpPropagationProtocol.computeDestinationsAtLevel(rtpEvent, _level);
      
      if (RtpEventRoutingProtocol_Debug) {
	System.out.println("Gateway at level (" + (_level + 1) + 
			   ") computing destinations at " + _level + 
			   ByteUtilities.printInt(dest));
      }
      
      /** This statement prevents a node from being stuck in a infinite
	  recursive process in the event that dest=0 */
      if (dest == 0) {
	return;
      }
      
      toReach.setDestinationsAtLevel(_level, dest);
      calculateDestinations(rtpEvent, toReach, (_level - 1));
    }
  }
  
  public static void main(String[] args) {
    int[] address = { 2, 3, 4, 5 };
    NodeAddress nodeAddress = new NodeAddress(address);
  }
}
